<?php
/**
 * @link https://www.len168.com
 * @copyright Copyright (c) 2020/12/3 len168.com
 * @author toshcn <toshcn@foxmail.com>
 */
namespace common\components\payment;

use Yii;

/**
 * 微信支付 （此接口 暂时未测试 ！！！！！！！！！！！）
 * Class WxPay
 * @package common\components\payment
 */
class WxPay extends DefaultPayment implements PaymentInterface
{
    public $apiKey = '';
    public $mchId = '';
    public $appId = '';
    public $notify = '';
    /**
     * 初始化
     */
    public function init()
    {
        parent::init();
    }
    /**
     * 配置组件
     * @param array $config 配置数组
     */
    public function setConfig($config)
    {
        $this->apiKey = isset($config['key']) ? $config['key'] : '';
        $this->mchId = isset($config['mchId']) ? $config['mchId'] : '';
        $this->appId = isset($config['appId']) ? $config['appId'] : '';
        $this->notify = isset($config['notifyUrl']) ? $config['notifyUrl'] : '';
    }

    /**
     * 统一下单
     * @param array $order 订单数据
     * string $order['title'] 商品描述
     * string $order['no'] 商户订单号
     * string $order['money'] 支付金额 分
     * string $order['ip'] IP
     * @return array
     * @throws \yii\base\Exception
     */
    public function createPayOrder($order = [], $form = 'app')
    {
        $key = $this->apiKey;
        $mchid = $this->mchId;
        $appid = $this->appId;

        $data['appid'] = $appid;
        $data['mch_id'] = $mchid;
        $data['sign_type'] = 'HMAC-SHA256';
        $data['body'] = $order['title'];//商品描述
        $data['out_trade_no'] = $order['no'];
        $data['total_fee'] = $order['money'];//订单总金额，单位为分
        $data['spbill_create_ip'] = $order['ip'];//终端IP
        $data['time_start'] = date('YmdHis');
        $data['nonce_str'] = Yii::$app->getSecurity()->generateRandomString(32);//随机字符串
        $data['notify_url'] = $this->notify;//接收微信支付异步通知回调地址
        $data['trade_type'] = 'APP';
        $data['sign'] = $this->getSign($data, $key, 'sha256');
        $xml = $this->arrayToXml($data);
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        $res = $this->wxHttpsRequest($xml, $url);

        if ($res) {
            $res = $this->xmlToArray($res);
            $res_sign = $res['sign'];
            unset($res['sign']);
            if ($this->getSign($res, $key, 'sha256') != $res_sign) {
                return ['status' =>0, 'message' => '返回的签名无效'];
            }
            if ($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS') {

                $arr = [
                    'appid' => $appid,
                    'partnerid' => $data['mch_id'],
                    'prepayid' => $res['prepay_id'],
                    'package' => 'Sign=WXPay',
                    'timestamp' => $data['time_start'],
                    'noncestr' => $data['nonce_str'],
                ];
                $arr['sign'] = $this->getSign($arr, $key, 'sha256');
                $arr['status'] = 1;
                $arr['err'] = '';
                unset($arr['appid']);
                return ['status' => 1, 'data' => $arr];
            }
            return ['status' => 0, 'message' => '接口调用失败：' . $res['return_msg']];
        }
        return ['status' => 0, 'message' => '无法生成支付订单'];
    }


    /**
     * 转账接口
     * @param array $order
     * string $order['title'] 订单标题
     * string $order['no'] 商户订单号，64个字符以内，可包含字母、数字、下划线，需保证在商户端不重复
     * string $order['money'] 订单总金额，单位为元
     * string $order['account'] 用户支付宝唯一用户号（2088开头的16位纯数字）
     * string $order['realname'] 转账对方的真实姓名
     * string $order['remark'] 转账备注
     * @throws \Exception
     */
    public function transMoney($order = [])
    {
       //todo
    }

    /**
     * 创建订单号
     * @param int $uid 用户id
     * @return string
     */
    public function createTradeNo($uid)
    {
        return 'w' . date('YmdHis') . $uid . mt_rand(1000, 99999);
    }

    /**
     * 异步通知验签
     * @param array $params 异步通知中收到的待验签的所有参数
     */
    public function verifyNotify($params = [])
    {
        //todo
    }

    /**
     * 查询支付状态
     * @param $orderno string 商户订单号
     * @return array
     * @throws \yii\base\Exception
     */
    public function orderQuery($orderno)
    {
        $key = $this->apiKey;
        $mchid = $this->mchId;
        $appid = $this->appId;

        $data['appid'] = $appid;
        $data['mch_id'] = $mchid;
        $data['sign_type'] = 'HMAC-SHA256';
        $data['out_trade_no'] = $orderno;
        $data['nonce_str'] = Yii::$app->security->generateRandomString();//随机字符串
        $data['sign'] = $this->getSign($data, $key, 'sha256');

        $xml = $this->arrayToXml($data);
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        $res = $this->wxHttpsRequest($xml, $url);
        $res = $this->xmlToArray($res);
        $res_sign = $res['sign'];
        unset($res['sign']);
        if ($this->getSign($res, $key, 'sha256') != $res_sign) {
            return ['status' => 0, 'message' => '返回的签名无效'];
        }
        if ($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS') {
            if ($res['trade_state'] == 'SUCCESS') {
                //支付成功
                return ['status' => 1, 'message' => '', 'total_fee' => $res['total_fee']];
            } elseif ($res['trade_state'] == 'USERPAYING') {
                return ['status' => 2, 'message' => '支付中'];
            }
        }
        return ['status' => 0, 'message' => '支付失败'];
    }

    /**
     * 企业付款
     * @param $arr
     * @param string $no
     * @return array|mixed
     * @throws \yii\base\Exception
     */
    public function sendMoneyToUser($arr, $no = '')
    {

        $key = $this->apiKey;
        $mchid = $this->mchId;
        $appid = $this->appId;
        $data['mch_appid'] = $appid;
        $data['mchid'] = $mchid;
        //随机字符串
        $data['nonce_str'] = Yii::$app->security->generateRandomString();
        $data['partner_trade_no'] = empty($no) ? md5($data['mchid'] . date("Ymd", time()) . date("His", time()) . rand(1111, 9999)) : $no;
        $data['openid'] = $arr['openid'];
        $data['check_name'] = 'NO_CHECK';
        $data['amount'] = intval($arr['fee']);
        $data['desc'] = '提现';
        $data['spbill_create_ip'] = Yii::$app->getRequest()->getUserIP();
        $data['sign'] = $this->getSign($data, $key);
        if (empty($data['mch_appid']) || empty($data['mchid'])) {

            $rearr = ['return_code' => 'FAIL', 'err_code_des' => '请先设置微信商户号和支付密钥'];
            return $rearr;
        }
        if (!$data['openid']) {

            $rearr = ['return_code' => 'FAIL', 'err_code_des' => '缺少用户openid'];
            return $rearr;
        }
        $xml = $this->arrayToXml($data);
        $url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
        $res = $this->wxHttpsRequestPem($xml, $url);
        $res = $this->xmlToArray($res);

        if ($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS') {
            //支付成功
            return $res;
        }

        return ['result_code' => $res['result_code'], 'err_code_des' => $res['err_code_des'] ? $res['err_code_des'] : $res['return_msg']];
    }

    //退款方法
    public function commonRefund($data)
    {
        $xml = $this->arrayToXml($data);
        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        $re = $this->wxHttpsRequestPem($xml, $url);
        if (!is_array($re)) {

            $re = $this->xmlToArray($re);
        }
        return $re;
    }

    /**
     * @param $paraMap
     * @param $urlencode
     * @return bool|string
     */
    function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {

            if ($urlencode) {

                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {

            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    /**
     * 签名
     * @param $obj array 参数数组
     * @param $key string 商户加密密匙
     * @param string $type 加密方式
     * @return bool|string
     */
    public function getSign($obj, $key, $type = 'sha256')
    {
        if (empty($obj) || !in_array($type, ['md5', 'sha256'])) {
            return false;
        }
        foreach ($obj as $k => $v) {
            if (!empty($v)) {
                $parameters[$k] = $v;
            }
        }
        ksort($parameters);
        $String = $this->formatBizQueryParaMap($parameters, false);
        $String = $String . "&key=" . $key;
        if ($type == 'md5') {
            $String = md5($String);
        } else if ($type == 'sha256') {
            $String = hash_hmac('sha256', $String, $key);
        }

        return strtoupper($String);
    }

    public function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
        }
        $xml .= "</xml>";
        return $xml;
    }

    public function xmlToArray($xml)
    {
        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $array_data;
    }

    public function wxHttpsRequestPem($vars, $url, $second = 30, $aHeader = array())
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'wxpay' . DIRECTORY_SEPARATOR . 'wxpayCertPublicKey.crt');
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'wxpay' . DIRECTORY_SEPARATOR . 'wxpayCertSecretKey.pem');
        if (count($aHeader) >= 1) {

            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
        $data = curl_exec($ch);
        curl_close($ch);
        if ($data) {
            return $data;
        } else {
            $error = curl_errno($ch);
            return ['status' => 0, 'message' => '检查是否设置了正确的支付证书'];
        }
    }

    public function wxHttpsRequest($vars, $url, $second = 30, $aHeader = array())
    {

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        if (count($aHeader) >= 1) {

            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
        $data = curl_exec($ch);
        if ($data) {
            curl_close($ch);
            return $data;
        }
        return false;
    }
}
